package cn.maihe.elg.operation.centers.hymake.channel;

import cn.maihe.elg.operation.centers.hymake.auth.HyMakeCenterAuthConfig;
import cn.maihe.elg.operation.centers.hymake.auth.HyMakeHeaderInfo;
import cn.maihe.elg.operation.centers.hymake.config.HyMakeInfoConfig;
import cn.maihe.elg.operation.centers.hymake.dto.HyMakeBaseReqDTO;
import cn.maihe.elg.operation.centers.hymake.dto.HyMakeBaseRespDTO;
import cn.maihe.elg.operation.centers.hymake.enums.HyMakeChannelMethod;
import cn.maihe.elg.operation.centers.hymake.enums.HyMakeRespCodeEnum;
import cn.maihe.elg.operation.common.TradeContext;
import cn.maihe.elg.operation.exception.CenterException;
import cn.maihe.elg.operation.model.dto.BaseResp;
import cn.maihe.elg.operation.model.enums.CenterNoEnum;
import cn.maihe.elg.operation.model.enums.InnerRespCode;
import cn.maihe.elg.operation.service.business.ExchangeCenterService;
import cn.maihe.elg.operation.utils.ElgLogger;
import cn.maihe.elg.operation.utils.IdGenerate;
import cn.maihe.elg.operation.utils.ValidateUtil;
import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cglib.beans.BeanMap;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;
import java.lang.reflect.ParameterizedType;
import java.net.SocketTimeoutException;
import java.util.Map;

/**
 * @Description 请求海迈中心基类服务
 * @Author WJH
 * @Date 2021/04/20
 */
@Service
@Slf4j
public abstract class HyMakeBaseChannelService<REQ extends HyMakeBaseReqDTO, RESP extends BaseResp> {

    @Resource
    private RestTemplate restTemplate;
    @Resource
    protected HyMakeInfoConfig hyMakeInfoConfig;
    @Resource
    private ExchangeCenterService exchangeCenterService;

    private final ObjectMapper objectMapper = new ObjectMapper();
    protected final Class<RESP> respClass;


    public HyMakeBaseChannelService() {
        respClass = (Class) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[1];
        objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    }

    /**
     * 设置公共字段：method&timestamp
     */
    protected REQ buildReq(REQ req, HyMakeCenterAuthConfig authConfig) {
        return req;
    }

    /**
     * 实际请求url
     */
    private String buildReqUrl(HyMakeCenterAuthConfig authConfig) {
        String reUrl = null;
        String url = authConfig.getCenterInfo().getUrl().trim();
        if (url.endsWith("/")) {
            reUrl = url + methodEnum().getMethod();
        } else {
            reUrl = url + "/" + methodEnum().getMethod();
        }
        //若有特殊配置指定地址则已指定地址为准
        if (methodEnum() == HyMakeChannelMethod.payResultReceive && StringUtils.isNotBlank(authConfig.getCenterInfo().getPayResultReceiveUrl())) {
            reUrl = authConfig.getCenterInfo().getPayResultReceiveUrl();
        } else if (methodEnum() == HyMakeChannelMethod.bondDownLoad && StringUtils.isNotBlank(authConfig.getCenterInfo().getBondDownLoadUrl())) {
            reUrl = authConfig.getCenterInfo().getBondDownLoadUrl();
        } else if (methodEnum() == HyMakeChannelMethod.guaranteeBackNotice && StringUtils.isNotBlank(authConfig.getCenterInfo().getGuaranteeBackNoticeUrl())) {
            reUrl = authConfig.getCenterInfo().getGuaranteeBackNoticeUrl();
        } else if (methodEnum() == HyMakeChannelMethod.claimResultReceive && StringUtils.isNotBlank(authConfig.getCenterInfo().getClaimResultReceiveUrl())) {
            reUrl = authConfig.getCenterInfo().getClaimResultReceiveUrl();
        }

        return reUrl;
    }

    public RESP doRequest(CenterNoEnum centerNoEnum, REQ req) {
        return this.doRequest(centerNoEnum, req, null);
    }

    /**
     * @param req     参与签名验签数据
     * @param attrMap 不参与签名验签附加属性
     * @return
     */
    public RESP doRequest(CenterNoEnum centerNoEnum, REQ req, Map<String, Object> attrMap) {

        HyMakeCenterAuthConfig authConfig = hyMakeInfoConfig.getAuthConfig(centerNoEnum);

        //构建上下文
        TradeContext context = new TradeContext(req, attrMap);
        try {
            //接口公共参数设置
            req = buildReq(req, authConfig);

            //请求参数校验
            verifyParam(context);
        } catch (Exception e) {
            CenterException ce = buildCException(InnerRespCode.FAILURE_PARAM, e, "参数错误:");
            ElgLogger.error(log, l -> l.error("traceId={} => 中心[{}][{}]服务 参数校验失败：{}", context.getTraceId(), centerNoEnum.getCenterNo(), methodEnum().getDesc(), ce.getMessage()));
            throw ce;
        }

        String clearBizReqBody = this.toJsonString(req);
        //宁德中心明文传输
        String encryptAndB64ReqBody = clearBizReqBody;
        if(centerNoEnum.equals(CenterNoEnum.QUANZHOU)){
            //业务数据sm2公钥加密 宁德中心明文传输
            try {
                encryptAndB64ReqBody = this.encrypt(clearBizReqBody, authConfig);
            } catch (Exception e) {
                CenterException ce = buildCException(InnerRespCode.FAILURE_PARAM, e, "加密失败:");
                ElgLogger.error(log, l -> l.error("traceId={} => 中心[{}][{}]服务 参数加密失败：{}", context.getTraceId(), centerNoEnum.getCenterNo(), methodEnum().getDesc(), ce.getMessage()));
                throw ce;
            }
        }

        HyMakeHeaderInfo hyMakeHeaderInfo = HyMakeHeaderInfo.builder()
                .appid(authConfig.getCenterInfo().getAppId())
                .requestid(IdGenerate.getIdStr())
                .timestamp(this.timestamp())
                .version(authConfig.getCenterInfo().getVersion())
                .build();
        try {
            String signature;
            //签名
            if(centerNoEnum.equals(CenterNoEnum.QUANZHOU)){
                signature = this.signatureSM3(hyMakeHeaderInfo, authConfig, clearBizReqBody);
            }else {
                signature = this.signature(hyMakeHeaderInfo, authConfig, clearBizReqBody);
            }
            hyMakeHeaderInfo.setSign(signature);
        } catch (Exception e) {
            CenterException ce = buildCException(InnerRespCode.FAILURE, e, "签名异常:");
            ElgLogger.error(log, l -> l.error("traceId={} => 中心[{}][{}]服务 签名异常：{}", context.getTraceId(), centerNoEnum.getCenterNo(), methodEnum().getDesc(), ce.getMessage()));
            throw ce;
        }

        //请求渠道服务
        String reqUrl = this.buildReqUrl(authConfig);
        try {
            REQ finalReq = req;
            ElgLogger.info(log, l -> l.info("traceId={} => 中心[{}][{}]服务 请求报文：{}", context.getTraceId(), centerNoEnum.getCenterNo(), methodEnum().getDesc(), this.toJsonString(finalReq)));
            ElgLogger.info(log, l -> l.info("traceId={} => 中心[{}][{}]服务 请求地址：{}", context.getTraceId(), centerNoEnum.getCenterNo(), methodEnum().getDesc(), reqUrl));
            HttpHeaders headers = new HttpHeaders();
            BeanMap.create(hyMakeHeaderInfo).forEach((k, v) -> {
                headers.add((String) k, (String) v);
            });
            headers.setContentType(MediaType.APPLICATION_JSON);

            HttpEntity<String> request = new HttpEntity<>(encryptAndB64ReqBody, headers);
            ResponseEntity<HyMakeBaseRespDTO> responseEntity = restTemplate.postForEntity(reqUrl, request, HyMakeBaseRespDTO.class);

            if (responseEntity == null || responseEntity.getBody() == null) {
                throw new RuntimeException("响应报文为空");
            } else if (!responseEntity.getStatusCode().is2xxSuccessful()) {
                throw new RuntimeException("响应报文http状态非成功[" + responseEntity.getStatusCodeValue() + ":" + responseEntity.getStatusCode().getReasonPhrase() + "]");
            } else {
                //正常响应业务处理
                HyMakeBaseRespDTO resp = responseEntity.getBody();
                if (!HyMakeRespCodeEnum.isSuccess(resp.getCode())) {
                    throw new RuntimeException("渠道侧接收失败:[" + resp.getCode() + "," + resp.getMessage() + "]");
                }

                ElgLogger.info(log, l -> l.info("traceId={} <= 中心[{}][{}]服务 响应报文：{}", context.getTraceId(),
                        centerNoEnum.getCenterNo(), methodEnum().getDesc(), this.toJsonString(resp)));

                RESP bizRespData;
                if (resp.getData() != null) {
                    bizRespData = JSON.parseObject((String) resp.getData(), this.respClass);
                } else {
                    bizRespData = JSON.parseObject(JSON.toJSONString(resp), this.respClass);
                }
                ElgLogger.info(log, l -> l.info("traceId={} <= 中心[{}][{}]服务 响应业务数：{}", context.getTraceId(),
                        centerNoEnum.getCenterNo(), methodEnum().getDesc(), this.toJsonString(bizRespData)));
                return bizRespData;
            }


        } catch (Exception e) {
            CenterException ce = buildCException(InnerRespCode.FAILURE, e, "请求响应异常:");
            ElgLogger.error(log, l -> l.error("traceId={} <= 中心[{}][{}]服务 请求响应异常：{}", context.getTraceId(),
                    centerNoEnum.getCenterNo(), methodEnum().getDesc(), ce.getMessage()));
            throw ce;
        }


    }

    private String signature(HyMakeHeaderInfo hyMakeHeaderInfo, HyMakeCenterAuthConfig authConfig, String clearBizReqBody) {
        return authConfig.getHyMakeAuthorization().signature(hyMakeHeaderInfo.getRequestid(),
                clearBizReqBody, hyMakeHeaderInfo.getTimestamp());
    }

    /**
     * SM3签名
     * @param hyMakeHeaderInfo
     * @param authConfig
     * @param clearBizReqBody
     * @return
     */
    private String signatureSM3(HyMakeHeaderInfo hyMakeHeaderInfo, HyMakeCenterAuthConfig authConfig, String clearBizReqBody) {
        return authConfig.getHyMakeAuthorization().signatureSM3(hyMakeHeaderInfo.getRequestid(),
                clearBizReqBody, hyMakeHeaderInfo.getTimestamp());
    }


    /**
     * 敏感字段加密处理
     *
     * @param clearBizReqBody
     * @return
     */
    private String encrypt(String clearBizReqBody, HyMakeCenterAuthConfig authConfig) {
        return authConfig.getHyMakeEncryption().encrypt(clearBizReqBody);
    }

    protected CenterException buildCException(InnerRespCode innerRespCode, Exception exception, String message) {
        CenterException ce;
        if (exception instanceof SocketTimeoutException) {
            ce = CenterException.timeout("超时:" + message.concat(exception.getMessage()), exception);
        } else if (exception instanceof CenterException) {
            ce = (CenterException) exception;
        } else {
            if (exception != null) {
                ce = CenterException.exception(message.concat(exception.getMessage()), exception);
            } else {
                ce = CenterException.of(innerRespCode, message, exception);
            }
        }
        return ce;
    }

    private void verifyParam(TradeContext<REQ, RESP> context) {
        ElgLogger.debug(log, l -> l.debug("traceId={} => [{}]服务 请求参数：{}", context.getTraceId(), methodEnum().getDesc(), toJsonString(context.getReq())));
        if (StringUtils.isBlank(methodEnum().getMethod())) {
            throw CenterException.of(InnerRespCode.FAILURE_PARAM, "method参数不能为空", null);
        }
        try {
            ValidateUtil.validate(context.getReq());
        } catch (Exception e) {
            throw CenterException.of(InnerRespCode.FAILURE_PARAM, "请求参数错误", e);
        }
    }

    /**
     * 渠道方法名
     *
     * @return
     */
    protected abstract HyMakeChannelMethod methodEnum();


    private String timestamp() {
        return String.valueOf(System.currentTimeMillis());
    }

    private String toJsonString(Object body) {
        if (body != null) {
            try {
                return objectMapper.writeValueAsString(body);
            } catch (JsonProcessingException e) {
                throw CenterException.exception("转换json字符串异常", e);
            }
        }
        return null;
    }

    protected CenterNoEnum getCenterNoEnumByAcceptOrderNo(String acceptOrderNo) {
        return exchangeCenterService.getCenterNoEnumByAcceptOrderNo(acceptOrderNo);
    }

    protected CenterNoEnum getCenterNoEnumById(Long centerId) {
        return exchangeCenterService.getCenterNoEnumById(centerId);
    }

}
